package service

import (
	"crypto/md5"
	"encoding/hex"
	"fmt"
	"io"
	"mime/multipart"
	"path/filepath"
	"strings"
	"time"

	"gitee.com/huangxinbo/xcas/internal/app/base"
	"gitee.com/huangxinbo/xcas/internal/dao"
	"gitee.com/huangxinbo/xcas/internal/model/db_model"
	"gitee.com/huangxinbo/xcas/pkg/storage"
	"github.com/gin-gonic/gin"
)

type FileService struct {
	base    *base.Base
	dao     *dao.DAO
	storage storage.Storage
	service *Service
}

func NewFileService(base *base.Base, dao *dao.DAO, service *Service) *FileService {
	// Create storage factory and get storage instance
	factory := storage.NewStorageFactory(base.Config.Upload)
	storageInstance, err := factory.CreateStorage()
	if err != nil {
		// Fallback to local storage if creation fails
		storageInstance = storage.NewLocalStorage(base.Config.Upload.LocalPath)
	}

	return &FileService{
		base:    base,
		dao:     dao,
		storage: storageInstance,
		service: service,
	}
}

// UploadFile handles file upload and saves to storage
func (s *FileService) UploadFile(ctx *gin.Context, fileHeader *multipart.FileHeader) (*db_model.File, error) {
	// Validate file size from config
	maxSize := int64(s.base.Config.Upload.MaxSize) << 20 // Convert MB to bytes
	if fileHeader.Size > maxSize {
		return nil, fmt.Errorf("file size exceeds limit: %dMB", s.base.Config.Upload.MaxSize)
	}

	// Open the uploaded file
	file, err := fileHeader.Open()
	if err != nil {
		return nil, fmt.Errorf("failed to open uploaded file: %v", err)
	}
	defer file.Close()

	// Read file content
	fileBytes, err := io.ReadAll(file)
	if err != nil {
		return nil, fmt.Errorf("failed to read file content: %v", err)
	}

	// Calculate MD5 hash for filename
	hash := md5.Sum(fileBytes)
	fileHash := hex.EncodeToString(hash[:])

	// Get file extension
	originalExt := filepath.Ext(fileHeader.Filename)

	// Generate unique filename with date prefix
	currentDate := time.Now().Format("20060102")
	filename := fmt.Sprintf("%s/%s%s", currentDate, fileHash, originalExt)

	// Upload file to storage
	filePath, err := s.storage.Upload(filename, fileBytes)
	if err != nil {
		return nil, fmt.Errorf("failed to upload file to storage: %v", err)
	}

	// Create file record in database
	fileRecord := &db_model.File{
		Type:         getStorageType(s.base.Config.Upload.StorageType),
		Data:         filePath,
		OriginalName: &fileHeader.Filename,
	}

	if err := s.dao.FileDAO.CreateFile(ctx, fileRecord); err != nil {
		// If database operation fails, remove the uploaded file
		s.storage.Delete(filePath)
		return nil, fmt.Errorf("failed to create file record: %v", err)
	}

	return fileRecord, nil
}

// getStorageType converts storage type string to database type value
func getStorageType(storageType string) int16 {
	switch storageType {
	case "local":
		return dao.StorageTypeLocal
	case "aliyun":
		return dao.StorageTypeOSS
	case "huawei":
		return dao.StorageTypeOBS
	case "tencent":
		return dao.StorageTypeCOS
	case "baidu":
		return dao.StorageTypeBOS
	case "aws":
		return dao.StorageTypeS3
	case "azure":
		return dao.StorageTypeAzure
	case "qiniu":
		return dao.StorageTypeQiniu
	case "google":
		return dao.StorageTypeGCS
	case "minio":
		return dao.StorageTypeMinIO
	default:
		return dao.StorageTypeLocal // default to local
	}
}

// GetFileByID retrieves file information by ID
func (s *FileService) GetFileByID(ctx *gin.Context, id string) (*db_model.File, error) {
	return s.dao.FileDAO.GetFileByID(ctx, id)
}

// DeleteFile deletes a file by ID
func (s *FileService) DeleteFile(ctx *gin.Context, id string) error {
	file, err := s.dao.FileDAO.GetFileByID(ctx, id)
	if err != nil {
		return err
	}

	// Delete file from storage
	if err := s.storage.Delete(file.Data); err != nil {
		return fmt.Errorf("failed to delete file from storage: %v", err)
	}

	// Delete file record from database
	return s.dao.FileDAO.DeleteFile(ctx, id)
}

// ListFiles retrieves files with pagination
func (s *FileService) ListFiles(ctx *gin.Context, offset, limit int) ([]db_model.File, int64, error) {
	return s.dao.FileDAO.ListFiles(ctx, offset, limit)
}

// GetFileContent retrieves file content by ID
func (s *FileService) GetFileContent(ctx *gin.Context, id string) ([]byte, string, error) {
	// Get file record from database
	file, err := s.dao.FileDAO.GetFileByID(ctx, id)
	if err != nil {
		return nil, "", fmt.Errorf("failed to get file record: %v", err)
	}

	// Download file content from storage
	content, err := s.storage.Download(file.Data)
	if err != nil {
		return nil, "", fmt.Errorf("failed to download file from storage: %v", err)
	}

	// Determine content type based on file extension
	contentType := "application/octet-stream"
	if file.OriginalName != nil {
		ext := strings.ToLower(filepath.Ext(*file.OriginalName))
		switch ext {
		case ".jpg", ".jpeg":
			contentType = "image/jpeg"
		case ".png":
			contentType = "image/png"
		case ".gif":
			contentType = "image/gif"
		case ".webp":
			contentType = "image/webp"
		case ".svg":
			contentType = "image/svg+xml"
		case ".ico":
			contentType = "image/x-icon"
		case ".bmp":
			contentType = "image/bmp"
		}
	}

	return content, contentType, nil
}

// GetFileURL returns the public URL for a file by ID
func (s *FileService) GetFileURL(ctx *gin.Context, id string) (string, error) {
	// Get file record from database
	file, err := s.dao.FileDAO.GetFileByID(ctx, id)
	if err != nil {
		return "", fmt.Errorf("failed to get file record: %v", err)
	}

	// Get public URL from storage
	url, err := s.storage.GetURL(file.Data)
	if err != nil {
		return "", fmt.Errorf("failed to get file URL: %v", err)
	}

	return url, nil
}
